/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.analyze.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JPanel;
import javax.swing.JScrollBar;

import com.cburch.logisim.analyze.model.Entry;
import com.cburch.logisim.analyze.model.TruthTable;
import com.cburch.logisim.analyze.model.TruthTableEvent;
import com.cburch.logisim.analyze.model.TruthTableListener;
import com.cburch.logisim.util.GraphicsUtil;
import static com.cburch.logisim.util.LocaleString.getFromLocale;

@SuppressWarnings("serial")
class TableTab extends JPanel implements TruthTablePanel, TabInterface {
    private static final Font HEAD_FONT = new Font("Serif", Font.BOLD, 14);
    private static final Font BODY_FONT = new Font("Serif", Font.PLAIN, 14);
    private static final int COLUMN_SEP = 8;
    private static final int HEADER_SEP = 4;

    private class MyListener implements TruthTableListener {
        @Override
        public void cellsChanged(TruthTableEvent event) {
            repaint();
        }

        @Override
        public void structureChanged(TruthTableEvent event) {
            computePreferredSize();
        }
    }

    private MyListener myListener = new MyListener();
    private TruthTable table;
    // reasonable start values
    private int cellWidth = 25;
    private int cellHeight = 15;
    private int tableWidth;
    private int tableHeight;
    private int provisionalX;
    private int provisionalY;
    private Entry provisionalValue = null;
    private TableTabCaret caret;
    private TableTabClip clip;

    public TableTab(TruthTable table) {
        this.table = table;
        table.addTruthTableListener(myListener);
        setToolTipText(" ");
        caret = new TableTabCaret(this);
        clip = new TableTabClip(this);
    }

    @Override
    public TruthTable getTruthTable() {
        return table;
    }

    TableTabCaret getCaret() {
        return caret;
    }

    void localeChanged() {
        computePreferredSize();
        repaint();
    }

    public int getColumn(MouseEvent event) {
        int x = event.getX() - (getWidth() - tableWidth) / 2;
        if (x < 0) return -1;
        int inputs = table.getInputColumnCount();
        int cols = inputs + table.getOutputColumnCount();
        int ret = (x + COLUMN_SEP / 2) / (cellWidth + COLUMN_SEP);
        if (inputs == 0) ret--;
        return ret >= 0 ? ret < cols ? ret : cols : -1;
    }

    int getColumnCount() {
        int inputs = table.getInputColumnCount();
        int outputs = table.getOutputColumnCount();
        return inputs + outputs;
    }

    @Override
    public int getOutputColumn(MouseEvent event) {
        int inputs = table.getInputColumnCount();
        if (inputs == 0) inputs = 1;
        int ret = getColumn(event);
        return ret >= inputs ? ret - inputs : -1;
    }

    @Override
    public int getRow(MouseEvent event) {
        int y = event.getY() - (getHeight() - tableHeight) / 2;
        if (y < cellHeight + HEADER_SEP) return -1;
        int ret = (y - cellHeight - HEADER_SEP) / cellHeight;
        int rows = table.getRowCount();
        return ret >= 0 ? ret < rows ? ret : rows : -1;
    }

    @Override
    public void setEntryProvisional(int y, int x, Entry value) {
        provisionalY = y;
        provisionalX = x;
        provisionalValue = value;

        int top = (getHeight() - tableHeight) / 2 + cellHeight + HEADER_SEP
            + y * cellHeight;
        repaint(0, top, getWidth(), cellHeight);
    }

    @Override
    public String getToolTipText(MouseEvent event) {
        int row = getRow(event);
        int col = getOutputColumn(event);
        Entry entry = table.getOutputEntry(row, col);
        return entry.getErrorMessage();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        caret.paintBackground(g);

        Dimension sz = getSize();
        int top = Math.max(0, (sz.height - tableHeight) / 2);
        int left = Math.max(0, (sz.width - tableWidth) / 2);
        int inputs = table.getInputColumnCount();
        int outputs = table.getOutputColumnCount();
        if (inputs == 0 && outputs == 0) {
            g.setFont(BODY_FONT);
            GraphicsUtil.drawCenteredText(g, getFromLocale("tableEmptyMessage"), sz.width / 2, sz.height / 2);
            return;
        }

        g.setColor(Color.GRAY);
        int lineX = left + (cellWidth + COLUMN_SEP) * inputs
            - COLUMN_SEP / 2;
        if (inputs == 0) lineX = left + cellWidth + COLUMN_SEP / 2;
        int lineY = top + cellHeight + HEADER_SEP / 2;
        g.drawLine(left, lineY, left + tableWidth, lineY);
        g.drawLine(lineX, top, lineX, top + tableHeight);

        g.setColor(Color.BLACK);
        g.setFont(HEAD_FONT);
        FontMetrics headerMetric = g.getFontMetrics();
        int x = left;
        int y = top + headerMetric.getAscent() + 1;
        if (inputs == 0) {
            x = paintHeader(getFromLocale("tableNullHeader"), x, y, g, headerMetric);
        } else {
            for (int i = 0; i < inputs; i++) {
                x = paintHeader(table.getInputHeader(i), x, y, g, headerMetric);
            }
        }
        if (outputs == 0) {
            x = paintHeader(getFromLocale("tableNullHeader"), x, y, g, headerMetric);
        } else {
            for (int i = 0; i < outputs; i++) {
                x = paintHeader(table.getOutputHeader(i), x, y, g, headerMetric);
            }
        }

        g.setFont(BODY_FONT);
        FontMetrics bodyMetric = g.getFontMetrics();
        y = top + cellHeight + HEADER_SEP;
        Rectangle clip = g.getClipBounds();
        int firstRow = Math.max(0, (clip.y - y) / cellHeight);
        int lastRow = Math.min(table.getRowCount(), 2 + (clip.y + clip.height - y) / cellHeight);
        y += firstRow * cellHeight;
        if (inputs == 0) left += cellWidth + COLUMN_SEP;
        boolean provisional = false;
        for (int i = firstRow; i < lastRow; i++) {
            x = left;
            for (int j = 0; j < inputs + outputs; j++) {
                Entry entry = j < inputs ? table.getInputEntry(i, j)
                        : table.getOutputEntry(i, j - inputs);
                if (provisionalValue != null && i == provisionalY
                        && j - inputs == provisionalX) {
                    provisional = true;
                    entry = provisionalValue;
                }
                if (entry.isError()) {
                    g.setColor(ERROR_COLOR);
                    g.fillRect(x, y, cellWidth, cellHeight);
                    g.setColor(Color.BLACK);
                }
                String label = entry.getDescription();
                int width = bodyMetric.stringWidth(label);
                if (provisional) {
                    provisional = false;
                    g.setColor(Color.GREEN);
                    g.drawString(label, x + (cellWidth - width) / 2,
                            y + bodyMetric.getAscent());
                    g.setColor(Color.BLACK);
                } else {
                    g.drawString(label, x + (cellWidth - width) / 2,
                            y + bodyMetric.getAscent());
                }
                x += cellWidth + COLUMN_SEP;
            }
            y += cellHeight;
        }

        caret.paintForeground(g);
    }

    int getCellWidth() { return cellWidth; }
    int getCellHeight() { return cellHeight; }

    int getX(int col) {
        Dimension sz = getSize();
        int left = Math.max(0, (sz.width - tableWidth) / 2);
        int inputs = table.getInputColumnCount();
        if (inputs == 0) left += cellWidth + COLUMN_SEP;
        return left + col * (cellWidth + COLUMN_SEP);
    }

    int getY(int row) {
        Dimension sz = getSize();
        int top = Math.max(0, (sz.height - tableHeight) / 2);
        return top + cellHeight + HEADER_SEP + row * cellHeight;
    }

    private int paintHeader(String header, int x, int y,
            Graphics g, FontMetrics fm) {
        int width = fm.stringWidth(header);
        g.drawString(header, x + (cellWidth - width) / 2, y);
        return x + cellWidth + COLUMN_SEP;
    }

    private void computePreferredSize() {
        int inputs = table.getInputColumnCount();
        int outputs = table.getOutputColumnCount();
        if (inputs == 0 && outputs == 0) {
            setPreferredSize(new Dimension(0, 0));
            return;
        }

        Graphics g = getGraphics();
        if (g == null) {
            cellHeight = 16;
            cellWidth = 24;
        } else {
            FontMetrics fm = g.getFontMetrics(HEAD_FONT);
            cellHeight = fm.getHeight();
            cellWidth = 24;
            if (inputs == 0 || outputs == 0) {
                cellWidth = Math.max(cellWidth, fm.stringWidth(getFromLocale("tableNullHeader")));
            }
            for (int i = 0; i < inputs + outputs; i++) {
                String header = i < inputs ? table.getInputHeader(i)
                        : table.getOutputHeader(i - inputs);
                cellWidth = Math.max(cellWidth, fm.stringWidth(header));
            }
        }

        if (inputs == 0) inputs = 1;
        if (outputs == 0) outputs = 1;
        tableWidth = (cellWidth + COLUMN_SEP) * (inputs + outputs) - COLUMN_SEP;
        tableHeight = cellHeight * (1 + table.getRowCount()) + HEADER_SEP;
        setPreferredSize(new Dimension(tableWidth, tableHeight));
        revalidate();
        repaint();
    }

    JScrollBar getVerticalScrollBar() {
        return new JScrollBar() {

            @Override
            public int getUnitIncrement(int direction) {
                int curY = getValue();
                if (direction > 0) {
                    return curY > 0 ? cellHeight : cellHeight + HEADER_SEP;
                } else {
                    return curY > cellHeight + HEADER_SEP ? cellHeight
                            : cellHeight + HEADER_SEP;
                }
            }

            @Override
            public int getBlockIncrement(int direction) {
                int curY = getValue();
                int curHeight = getVisibleAmount();
                int numCells = curHeight / cellHeight - 1;
                if (numCells <= 0) numCells = 1;
                if (direction > 0) {
                    return curY > 0
                        ? numCells * cellHeight
                        : numCells * cellHeight + HEADER_SEP;
                } else {
                    return curY > cellHeight + HEADER_SEP
                        ? numCells * cellHeight
                        : numCells * cellHeight + HEADER_SEP;
                }
            }
        };
    }

    @Override
    public void copy() {
        requestFocus();
        clip.copy();
    }

    @Override
    public void paste() {
        requestFocus();
        clip.paste();
    }

    @Override
    public void delete() {
        requestFocus();
        int r0 = caret.getCursorRow();
        int r1 = caret.getMarkRow();
        int c0 = caret.getCursorCol();
        int c1 = caret.getMarkCol();
        if (r0 < 0 || r1 < 0) return;
        if (r1 < r0) { int t = r0; r0 = r1; r1 = t; }
        if (c1 < c0) { int t = c0; c0 = c1; c1 = t; }
        int inputs = table.getInputColumnCount();
        for (int c = c0; c <= c1; c++) {
            if (c >= inputs) {
                for (int r = r0; r <= r1; r++) {
                    table.setOutputEntry(r, c - inputs, Entry.DONT_CARE);
                }
            }
        }
    }

    @Override
    public void selectAll() {
        caret.selectAll();
    }
}
